/*
 *  Copyright (c) 2010 Stephen Nelson <stephen@sfnelson.org>
 *
 *  Permission is hereby granted, free of charge, to any person obtaining a copy
 *  of this software and associated documentation files (the "Software"), to deal
 *  in the Software without restriction, including without limitation the rights
 *  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 *  copies of the Software, and to permit persons to whom the Software is
 *  furnished to do so, subject to the following conditions:
 *
 *  The above copyright notice and this permission notice shall be included in
 *  all copies or substantial portions of the Software.
 *
 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 *  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 *  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 *  THE SOFTWARE.
 */

package org.sfnelson.bma.server;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.Properties;

import com.google.gwt.user.server.rpc.RemoteServiceServlet;

import authenticator.AuthData;
import authenticator.CodeCalculator;
import authenticator.ServerCommunication;
import com.google.appengine.api.users.User;
import com.google.appengine.api.users.UserServiceFactory;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import org.sfnelson.bma.client.Key;
import org.sfnelson.bma.client.KeyService;
import org.sfnelson.bma.client.NotAuthenticatedException;
import org.sfnelson.bma.client.UserData;

/**
 * The server side implementation of the RPC service.
 */
@SuppressWarnings("serial")
public class KeyServiceImpl extends RemoteServiceServlet implements KeyService {

	private User getUser() throws NotAuthenticatedException {
		User user = UserServiceFactory.getUserService().getCurrentUser();

		if (user == null) {
			throw new NotAuthenticatedException();
		}

		return user;
	}

	public Key retrieveKey() throws NotAuthenticatedException {
		SecretStore store = SecretStore.getStore(getUser().getUserId());

		if (store == null || store.getSerial() == null) {
			return null;
		}

		String key = CodeCalculator.calculateCurrentCode(store.getToken(), store.getTimeOffset());
		long remaining = 30000 - (System.currentTimeMillis() + store.getTimeOffset()) % 30000;

		Key k = new Key();
		k.value = key;
		k.valid = (int) remaining;
		return k;
	}

	public UserData retrieveSecret(String serial, String token) throws IOException {
		AuthData data = ServerCommunication.restore(serial, token);

		return storeSecret(data);
	}

	public UserData storeSecret(AuthData data) throws NotAuthenticatedException {
		SecretStore.setSecret(getUser().getUserId(), data.getSerial(), data.getSecret(), data.getOffset());

		return retrieveUserData();
	}

	public UserData retrieveUserData() throws NotAuthenticatedException {
		SecretStore store = SecretStore.getStore(getUser().getUserId());

		String logout = UserServiceFactory.getUserService().createLogoutURL("/");

		if (store == null) {
			return new UserData(getUser().getNickname(), logout, null, 0l);
		}

		return new UserData(getUser().getNickname(), logout,
				store.getSerial(), store.getTimeOffset());
	}

	public UserData sync() throws NotAuthenticatedException, IOException {
		SecretStore store = SecretStore.getStore(getUser().getUserId());

		if (store == null || store.getSerial() == null) {
			return retrieveUserData();
		}

		long offset = ServerCommunication.getServerTime();

		if (offset != store.getTimeOffset()) {
			SecretStore.setSecret(getUser().getUserId(), store.getSerial(), store.getToken(), offset);
		}

		return retrieveUserData();
	}

	public UserData generateUserData() throws IOException {
		AuthData data = ServerCommunication.getAuthenticatorInitialization("US");

		return storeSecret(data);
	}

	public void backupUserData() {
		SecretStore data = SecretStore.getStore(getUser().getUserId());

		String msg = "Here are the details for your mobile authenticator account. You can "
				+ "use these details at https://bmauth.appspot.com/, use another "
				+ "emulator, or you can use one of blizzard's mobile apps.\n\n";

		sendMail(getUser(), data, msg);
	}

	public UserData deleteUserData() {
		SecretStore data = SecretStore.getStore(getUser().getUserId());

		String msg = "You have deleted your mobile authenticator account successfully.\n\n"
				+ "In case you want to reactivate it later, we've attached your restore details "
				+ "below. You can enter them again at https://bmauth.appspot.com/, use another "
				+ "emulator, or you can use one of blizzard's mobile apps.\n\n";

		sendMail(getUser(), data, msg);

		return storeSecret(new AuthData(null, null, new byte[]{0}, 0));
	}

	private void sendMail(User user, SecretStore data, String message) {
		Properties props = new Properties();
		Session session = Session.getDefaultInstance(props, null);

		String code = data.buildRestoreCode();

		String msgBody = message
				+ "Serial Number:\t" + data.getSerial() + "\n"
				+ "Restore Code:\t" + code + "\n\n"
				+ "Secret:\t" + data.getTokenString() + "\n\n";

		try {
			Message msg = new MimeMessage(session);
			msg.setFrom(new InternetAddress("bmauth@sfnelson.org", "Mobile Authenticator Administrator"));
			msg.addRecipient(Message.RecipientType.TO,
					new InternetAddress(user.getEmail()));
			msg.setSubject("Blizzard Mobile Authenticator Account");
			msg.setText(msgBody);
			Transport.send(msg);

		} catch (AddressException e) {
			e.printStackTrace();
		} catch (MessagingException e) {
			e.printStackTrace();
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		}
	}
}
